from twisted.internet import defer, reactor, protocol 
from twisted.protocols import basic
from twisted.python import failure
import os, os.path, sys, StringIO, weakref

class UnknownCommand(Exception):
    pass

class PythonException(Exception):
    pass

class Runner(object):
    def __init__(self, protocol):
        self._protocol = weakref.ref(protocol)
        self._globals = {}
        self._locals = {}
        self._cwd = os.getcwd()

    def parse_cwd(self, rest):
        return 'print', [self._cwd]

    def parse_ls(self, rest):
        return 'pprint', os.listdir(self._cwd)

    def parse_echo(self, rest):
        return 'print', [rest]

    parse_e = parse_echo

    def parse_ping(self, rest):
        return 'ping', [rest]

    def parse_cd(self, rest):
        curr = self._cwd
        if rest == '..':
            path = os.path.dirname(curr)
        else:
            path = os.path.join(curr, rest)
        self._cwd = path
        return self.parse_cwd(None)

    def parse_python(self, rest):
        try:
            code = compile(rest + '\n', '<remote>', 'single')
            sys.stdout = StringIO.StringIO()
            exec code in self._globals, self._locals
            return 'print', [sys.stdout.getvalue()]
        except:
            exc_type, exc_val, exc_tb = sys.exc_info()
            raise PythonException(exc_type, exc_val)
        finally:
            sys.stdout = sys.__stdout__

    parse_p = parse_python

    def parse_cnt(self, rest):
        return 'pprint', [str(cnt) for cnt in self._protocol().factory.connections]
    
    def parse(self, line):
        parts = line.split(None, 1)
        if not parts:
            raise UnknownCommand('<empty>')
        if len(parts) > 1:
            cmd, rest =  parts
        else:
            cmd, rest = parts[0], ''
        call = getattr(self, 'parse_'+cmd, None)
        if call is None:
            raise UnknownCommand(cmd)
        return call(rest)



class RemoteServerProtocol(basic.LineOnlyReceiver):
    def __init__(self):
        self.runner = Runner(self)

    def lineReceived(self, line):
        return defer.execute(self.runner.parse, line)\
            .addErrback(self.processError)\
            .addErrback(self.processUnknownError)\
            .addCallback(self.sendAnswer)

    def connectionMade(self):
        print 'Connection made'
        self.factory.connections.append(self.transport.getPeer())

    def connectionLost(self, ex):
        print 'Connection lost', ex
        self.factory.connections.remove(self.transport.getPeer())

    def sendAnswer(self, arg):
        cmd, args = arg
        self.sendLine(cmd+' '+' '.join(args))

    def processError(self, err):
        err.trap(UnknownCommand, PythonException)
        return ('error', [err.type.__name__+' '+str(err.value)])

    def processUnknownError(self, err):
        return ('error', ['Exception occured: '+str(err)])

class RemoteServerFactory(protocol.ServerFactory):
    protocol = RemoteServerProtocol
    def __init__(self):
        self.connections = []


def run():
    print 'Starting server'
    reactor.listenTCP(1234, RemoteServerFactory())
    reactor.run()

if __name__ == '__main__':
    run()
